package com.github.framework.aop.support;

import com.github.framework.aop.aspect.Advice;
import com.github.framework.aop.config.AopConfig;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @program: springv2
 * @description:
 * @author: Pop
 * @create: 2021-06-27 21:04
 **/
public class AdviceSupport {

    private AopConfig config;
    private Object target;
    private Class targetClass;
    private Pattern pointCutClassPattern;

    public AdviceSupport(AopConfig config) {
        this.config = config;
    }

    public AopConfig getConfig() {
        return config;
    }

    public void setConfig(AopConfig config) {
        this.config = config;
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
        parse();
    }

    public Class getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class targetClass) {
        this.targetClass = targetClass;
    }



    // 记录了每个方法的回调
//    private Map<Method, Map<String, Advice>> methodCache;
    // 按照Spring 的进行改造 原版是 MethodCacheKey 其实就是 Method
    private Map<Method, List<Object>> methodCache;

    /**
     * 从缓存中获得一个调用链
     * @param method
     * @param targetClass
     * @return
     */
    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
        return null;
    }

    /**
     * 进行切面操作的初始化
     */
    private void parse(){
        // pointCut=public .* com.github.demo.service..*Service..*(.*) 替换成正则表达式
        String pointCut =  this.config.getPointCut()
                            .replaceAll("\\.","\\\\.")
                            .replaceAll("\\\\.\\*",".*")
                            .replaceAll("\\(","\\\\(")
                            .replaceAll("\\)","\\\\)");
        //Method.getName() 会含有几段的含义
        //1.修饰符 2.返回值 3.方法名包括参数列表等

        // 切出class的匹配规则,将类名切出来
        // public .* com.github.demo.service..*Service
        String pointCutForClassRegex = pointCut.substring(0,pointCut.lastIndexOf("\\(")-4);
        //  com.github.demo.service..*Service
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
        //  将内容缓存起来
//        methodCache = new HashMap<Method, Map<String, Advice>>();
        methodCache = new HashMap<Method, List<Object>>();
        // 创造出匹配方法的正则
        Pattern pointCutPattern = Pattern.compile(pointCut);
        try{

            Class aspectClass = Class.forName(this.config.getAspectClass());
            // 将方法缓存起来
            Map<String,Method> aspectMethods = new HashMap<String, Method>();
            //将配置好的切片的类进行缓存
            for(Method method : aspectClass.getMethods())
            {
                aspectMethods.put(method.getName(),method);
            }

            // 对目标对象进行匹配，是否要进行织入操作
            for(Method method : this.targetClass.getMethods())
            {
                //对一些可能带有抛出异常的方法，这里进行一下删除
                String methodString = method.toString();
                if(methodString.contains("throws"))
                {
                    methodString = methodString.substring(0,methodString.lastIndexOf("throws")).trim();
                }
                Matcher matcher = pointCutPattern.matcher(methodString);
                if(matcher.matches())
                {   // 确定是需要aop的方法
                    Map<String,Advice> advices = new HashMap<String, Advice>();

                    if(!(null==config.getAspectBefore()||"".equals(config.getAspectBefore()))){
                        // 确实配置了，就将切面的对象和方法封装起来
                        advices.put("before",new Advice(aspectClass.newInstance(),
                                aspectMethods.get(this.config.getAspectBefore())));
                    }

                    if(!(null==config.getAspectAfter()||"".equals(config.getAspectAfter()))){
                    // 确实配置了，就将切面的对象和方法封装起来
                        advices.put("after",new Advice(aspectClass.newInstance(),
                            aspectMethods.get(this.config.getAspectAfter())));
                    }

                    if(!(null==config.getAspectAfterThrow()||"".equals(config.getAspectAfterThrow()))){
                        // 确实配置了，就将切面的对象和方法封装起来
                        Advice advice = new Advice(aspectClass.newInstance(),
                                aspectMethods.get(this.config.getAspectBefore()));
                        advice.setThrowName(this.config.getAspectAfterThrowingName());
                        advices.put("afterThrowing",advice);
                    }

                    //跟目标代理类的业务方法和Advices建立一对多个关联关系，
                    // 以便在Porxy类中获得
//                    methodCache.put(method,advices);
                }
            }

        }catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    /** 这个方法将会被废弃，按照Spring的实现，将会被 getInterceptorsAndDynamicInterceptionAdvice 方法所替代。
     * //根据一个目标代理类的方法，获得其对应的通知
     * @param method
     * @param o
     * @return
     */
//    public Map<String, Advice> getAdvices(Method method, Object o) throws Exception{
//        //享元设计模式的应用
//        Map<String,Advice> cache = methodCache.get(method);
//        if(null == cache){
//            // 由于传进来的方法可能不是targetClass的方法，也许是其他接口的方法，因为这
//            // 里是代理的时候invoke调用，所有在当前methodCache方法找不到时，需要再次
//            // 去目标方法去找找看，如果实在找不到就算了
//            Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
//            cache = methodCache.get(m);
//            this.methodCache.put(m,cache);
//        }
//        return cache;
//    }

    /**
     * 判断配置文件是否符合成为代理的规则
     * @return
     */
    public boolean pointCutMath() {
        return pointCutClassPattern.matcher(this.targetClass.toString()).matches();
    }
}